"use strict";
/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
let checkNumber = 0;
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
// The rest of this file is the actual ray tracer written by Adam
// Burmister. It's a concatenation of the following files:
//
//   flog/color.js
//   flog/light.js
//   flog/vector.js
//   flog/ray.js
//   flog/scene.js
//   flog/material/basematerial.js
//   flog/material/solid.js
//   flog/material/chessboard.js
//   flog/shape/baseshape.js
//   flog/shape/sphere.js
//   flog/shape/plane.js
//   flog/intersectioninfo.js
//   flog/camera.js
//   flog/background.js
//   flog/engine.js
let debug = false;
function debugLog(msg) {
    if (debug) {
        console.log(msg);
    }
}
const MAX_COLOR_VALUE = 255;
const WEIGHT_RED = 77;
const WEIGHT_GREEN = 150;
const WEIGHT_BLUE = 29;
const NUMBER_TWO = 2.0;
const NUMBER_THREE = 3;
const NUMBER_FIVE = 5;
const NUMBER_EIGHT = 8;
const NUMBER_TEN = 10.0;
const NUMBER_ONE_POINT_TWO = 1.2;
const NUMBER_ONE_POINT_FIVE = 1.5;
const NUMBER_POINT_ONE = 0.1;
const NUMBER_POINT_TWO = 0.2;
const NUMBER_POINT_TWO_FIVE = 0.25;
const NUMBER_POINT_THREE = 0.3;
const NUMBER_POINT_FOUR = 0.4;
const NUMBER_POINT_FIVE = 0.5;
const NUMBER_POINT_SEVEN = 0.7;
const NUMBER_POINT_EIGHT = 0.8;
const NUMBER_POINT_NINE = 0.9;
const NUMBER_ONE_THOUSAND = 1000;
const NUMBER_ONE_HUNDRED = 100;
const CHECK_NUMBER = 2321;
const MAX_DISTANCE = 2000;
const DEFAULT_CANVAS_HEIGHT = 100;
const DEFAULT_CANVAS_WIDTH = 100;
const DEFAULT_VECTOR_Z = -15;
const MAX_RUN_TIMES = 20;
// Defined class Color
class Color {
    constructor(r, g, b) {
        this.red = 0.0;
        this.green = 0.0;
        this.blue = 0.0;
        this.red = r !== null && r !== void 0 ? r : 0.0;
        this.green = g !== null && g !== void 0 ? g : 0.0;
        this.blue = b !== null && b !== void 0 ? b : 0.0;
    }
    static add(c1, c2) {
        let result = new Color(0, 0, 0);
        result.red = c1.red + c2.red;
        result.green = c1.green + c2.green;
        result.blue = c1.blue + c2.blue;
        return result;
    }
    static addScalar(c1, s) {
        let result = new Color(0, 0, 0);
        result.red = c1.red + s;
        result.green = c1.green + s;
        result.blue = c1.blue + s;
        result.limit();
        return result;
    }
    subtract(c1, c2) {
        let result = new Color(0, 0, 0);
        result.red = c1.red - c2.red;
        result.green = c1.green - c2.green;
        result.blue = c1.blue - c2.blue;
        return result;
    }
    static multiply(c1, c2) {
        let result = new Color(0, 0, 0);
        result.red = c1.red * c2.red;
        result.green = c1.green * c2.green;
        result.blue = c1.blue * c2.blue;
        return result;
    }
    static multiplyScalar(c1, f) {
        let result = new Color(0, 0, 0);
        result.red = c1.red * f;
        result.green = c1.green * f;
        result.blue = c1.blue * f;
        return result;
    }
    divideFactor(c1, f) {
        let result = new Color(0, 0, 0);
        result.red = c1.red / f;
        result.green = c1.green / f;
        result.blue = c1.blue / f;
        return result;
    }
    limit() {
        this.red = this.red > 0.0 ? (this.red > 1.0 ? 1.0 : this.red) : 0.0;
        this.green = this.green > 0.0 ? (this.green > 1.0 ? 1.0 : this.green) : 0.0;
        this.blue = this.blue > 0.0 ? (this.blue > 1.0 ? 1.0 : this.blue) : 0.0;
    }
    distance(color) {
        let d = Math.abs(this.red - color.red) +
            Math.abs(this.green - color.green) +
            Math.abs(this.blue - color.blue);
        return d;
    }
    static blend(c1, c2, w) {
        let result = new Color(0, 0, 0);
        result = Color.add(Color.multiplyScalar(c1, 1 - w), Color.multiplyScalar(c2, w));
        return result;
    }
    brightness() {
        let r = Math.floor(this.red * MAX_COLOR_VALUE);
        let g = Math.floor(this.green * MAX_COLOR_VALUE);
        let b = Math.floor(this.blue * MAX_COLOR_VALUE);
        return ((r * WEIGHT_RED + g * WEIGHT_GREEN + b * WEIGHT_BLUE) >> NUMBER_EIGHT);
    }
    toString() {
        let r = Math.floor(this.red * MAX_COLOR_VALUE);
        let g = Math.floor(this.green * MAX_COLOR_VALUE);
        let b = Math.floor(this.blue * MAX_COLOR_VALUE);
        return 'rgb(' + r + ',' + g + ',' + b + ')';
    }
}
// Defined class Light
class Light {
    constructor(pos, c, i = NUMBER_TEN) {
        this.intensity = NUMBER_TEN;
        this.position = pos;
        this.color = c;
        this.intensity = i;
    }
    toString() {
        return ('Light [' +
            this.position.x +
            ',' +
            this.position.y +
            ',' +
            this.position.z +
            ']');
    }
}
// Defined class Vector
class Vector {
    constructor(xF, yF, zF) {
        this.x = 0.0;
        this.y = 0.0;
        this.z = 0.0;
        this.x = xF !== null && xF !== void 0 ? xF : 0;
        this.y = yF !== null && yF !== void 0 ? yF : 0;
        this.z = zF !== null && zF !== void 0 ? zF : 0;
    }
    copy(vector) {
        this.x = vector.x;
        this.y = vector.y;
        this.z = vector.z;
    }
    normalize() {
        let m = this.magnitude();
        return new Vector(this.x / m, this.y / m, this.z / m);
    }
    magnitude() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }
    cross(w) {
        return new Vector(-this.z * w.y + this.y * w.z, this.z * w.x - this.x * w.z, -this.y * w.x + this.x * w.y);
    }
    dot(w) {
        return this.x * w.x + this.y * w.y + this.z * w.z;
    }
    static add(v, w) {
        return new Vector(w.x + v.x, w.y + v.y, w.z + v.z);
    }
    static subtract(v, w) {
        if (!w || !v) {
            console.log('Vectors must be defined');
        }
        return new Vector(v.x - w.x, v.y - w.y, v.z - w.z);
    }
    multiplyVector(v, w) {
        return new Vector(v.x * w.x, v.y * w.y, v.z * w.z);
    }
    static multiplyScalar(v, w) {
        return new Vector(v.x * w, v.y * w, v.z * w);
    }
    toString() {
        return 'Vector [' + this.x + ',' + this.y + ',' + this.z + ']';
    }
}
// Defined class Ray
class Ray {
    constructor(pos, dir) {
        this.position = pos;
        this.direction = dir;
    }
    toString() {
        return 'Ray [' + this.position + ',' + this.direction + ']';
    }
}
// Defined class Scene
class Scene {
    constructor() {
        this.camera = new Camera(new Vector(0, 0, -NUMBER_FIVE), new Vector(0, 0, 1), new Vector(0, 1, 0));
        this.shapes = Array();
        this.lights = Array();
        this.background = new Background(new Color(0, 0, NUMBER_POINT_FIVE), NUMBER_POINT_TWO);
    }
}
// Defined class BaseMaterial
class BaseMaterial {
    constructor() {
        this.gloss = NUMBER_TWO;
        this.transparency = 0.0;
        this.reflection = 0.0;
        this.refraction = NUMBER_POINT_FIVE;
        this.hasTexture = false;
    }
    getColor(u, v) {
        return new Color(0, 0, 0);
    }
    wrapUp(t) {
        t = t % NUMBER_TWO;
        if (t < -1) {
            t += NUMBER_TWO;
        }
        if (t >= 1) {
            t -= NUMBER_TWO;
        }
        return t;
    }
    toString() {
        return ('Material [gloss=' +
            this.gloss +
            ', transparency=' +
            this.transparency +
            ', hasTexture=' +
            this.hasTexture +
            ']');
    }
}
// Defined class Solid
class Solid extends BaseMaterial {
    constructor(c, refle, refra, tra, g) {
        super();
        this.color = c;
        this.reflection = refle;
        this.transparency = tra;
        this.gloss = g;
        this.hasTexture = false;
    }
    getColor(u, v) {
        return this.color;
    }
    toString() {
        return ('SolidMaterial [gloss=' +
            this.gloss +
            ', transparency=' +
            this.transparency +
            ', hasTexture=' +
            this.hasTexture +
            ']');
    }
}
// Defined class Chessboard
class Chessboard extends BaseMaterial {
    constructor(cEven, cOdd, refle, tra, g, den) {
        super();
        this.density = NUMBER_POINT_FIVE;
        this.colorEven = cEven;
        this.colorOdd = cOdd;
        this.reflection = refle;
        this.transparency = tra;
        this.gloss = g;
        this.density = den;
        this.hasTexture = true;
    }
    getColor(u, v) {
        let t = this.wrapUp(u * this.density) * this.wrapUp(v * this.density);
        if (t < 0.0) {
            return this.colorEven;
        }
        else {
            return this.colorOdd;
        }
    }
    toString() {
        return ('ChessMaterial [gloss=' +
            this.gloss +
            ', transparency=' +
            this.transparency +
            ', hasTexture=' +
            this.hasTexture +
            ']');
    }
}
// Defined class Baseshape
class Baseshape {
    constructor() {
        this.position = new Vector(0, 0, 0);
        this.radius = 0.0;
        this.material = new BaseMaterial();
    }
    intersect(ray) {
        return null;
    }
}
// Defined class Sphere
class Sphere extends Baseshape {
    constructor(pos, rad, mater) {
        super();
        this.radius = rad;
        this.position = pos;
        this.material = mater;
    }
    intersect(ray) {
        var _a;
        let info = new IntersectionInfo();
        info.shape = this;
        let dst = Vector.subtract(ray.position, this.position);
        let B = dst.dot(ray.direction);
        let C = dst.dot(dst) - this.radius * this.radius;
        let D = B * B - C;
        if (D > 0) {
            info.isHit = true;
            info.distance = -B - Math.sqrt(D);
            info.position = Vector.add(ray.position, Vector.multiplyScalar(ray.direction, info.distance));
            info.normal = Vector.subtract(info.position, this.position).normalize();
            info.color = (_a = this.material) === null || _a === void 0 ? void 0 : _a.getColor(0, 0);
        }
        else {
            info.isHit = false;
        }
        return info;
    }
    toString() {
        return ('Sphere [position=' + this.position + ', radius=' + this.radius + ']');
    }
}
// Defined class Plane
class Plane extends Baseshape {
    constructor(pos, dF, mater) {
        super();
        this.d = 0.0;
        this.position = pos;
        this.d = dF;
        this.material = mater;
    }
    intersect(ray) {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        let info = new IntersectionInfo();
        let vD = (_b = (_a = this.position) === null || _a === void 0 ? void 0 : _a.dot(ray.direction)) !== null && _b !== void 0 ? _b : 0;
        if (vD === 0) {
            return info;
        }
        let t = -(((_c = this.position) === null || _c === void 0 ? void 0 : _c.dot(ray.position)) + this.d) / vD;
        if (t <= 0) {
            return info;
        }
        info.shape = this;
        info.isHit = true;
        info.position = Vector.add(ray.position, Vector.multiplyScalar(ray.direction, t));
        info.normal = this.position;
        info.distance = t;
        if (((_d = this.material) === null || _d === void 0 ? void 0 : _d.hasTexture) != null) {
            let vU = new Vector(this.position.y, this.position.z, -this.position.x);
            let vV = vU.cross(this.position);
            let u = (_e = info.position) === null || _e === void 0 ? void 0 : _e.dot(vU);
            let v = (_f = info.position) === null || _f === void 0 ? void 0 : _f.dot(vV);
            info.color = (_g = this.material) === null || _g === void 0 ? void 0 : _g.getColor(u, v);
        }
        else {
            info.color = (_h = this.material) === null || _h === void 0 ? void 0 : _h.getColor(0, 0);
        }
        return info;
    }
    toString() {
        return 'Plane [' + this.position + ', d=' + this.d + ']';
    }
}
// Defined class IntersectionInfo
class IntersectionInfo {
    constructor() {
        this.isHit = false;
        this.hitCount = 0;
        this.shape = new Baseshape();
        this.position = new Vector(0, 0, 0);
        this.normal = new Vector(0, 0, 0);
        this.color = new Color(0, 0, 0);
        this.distance = 0;
    }
    toString() {
        return 'Intersection [' + this.position + ']';
    }
}
// Defined class Camera
class Camera {
    constructor(pos, lookV, upV) {
        this.position = pos;
        this.lookAt = lookV;
        this.up = upV;
        this.equator = this.lookAt.normalize().cross(this.up);
        this.screne = Vector.add(this.position, this.lookAt);
    }
    getRay(vx, vy) {
        let pos = Vector.subtract(this.screne, Vector.subtract(Vector.multiplyScalar(this.equator, vx), Vector.multiplyScalar(this.up, vy)));
        pos.y *= -1;
        let dir = Vector.subtract(pos, this.position);
        let ray = new Ray(pos, dir.normalize());
        return ray;
    }
    toString() {
        return 'Ray []';
    }
}
// Defined class Background
class Background {
    constructor(c, amb) {
        this.ambience = 0.0;
        this.color = c;
        this.ambience = amb;
    }
}
// Defined class Engine
class Engine {
    constructor(opt) {
        this.options = opt;
        this.options.canvasHeight /= this.options.pixelHeight;
        this.options.canvasWidth /= this.options.pixelWidth;
    }
    setPixel(x, y, color) {
        if (x === y) {
            checkNumber += color.brightness();
        }
    }
    renderScene(scene) {
        checkNumber = 0;
        let canvasHeight = this.options.canvasHeight;
        let canvasWidth = this.options.canvasWidth;
        for (let y = 0; y < canvasHeight; y++) {
            for (let x = 0; x < canvasWidth; x++) {
                let yp = ((y * 1.0) / canvasHeight) * NUMBER_TWO - 1;
                let xp = ((x * 1.0) / canvasWidth) * NUMBER_TWO - 1;
                let ray = scene.camera.getRay(xp, yp);
                let color = this.getPixelColor(ray, scene);
                this.setPixel(x, y, color);
                // debugLog("y = " + y + ", x = " + x + ", ray.position = " + ray.position?.toString());
                // debugLog("y = " + y + ", x = " + x + ", ray.direction = " + ray.direction?.toString());
                // debugLog("y = " + y + ", x = " + x + ", color = " + color.toString());
            }
        }
        // debugLog("checkNumber = " + checkNumber);
        if (checkNumber !== CHECK_NUMBER) {
            console.log('Scene rendered incorrectly');
        }
    }
    getPixelColor(ray, scene) {
        let info = this.testIntersection(ray, scene, null);
        if (info.isHit) {
            let color = this.rayTrace(info, ray, scene, 0);
            return color;
        }
        return scene.background.color;
    }
    testIntersection(ray, scene, exclude) {
        let hits = 0;
        let best = new IntersectionInfo();
        best.distance = MAX_DISTANCE;
        for (let i = 0; i < scene.shapes.length; i++) {
            let shape = scene.shapes[i];
            if (shape !== exclude) {
                let info = shape.intersect(ray);
                if (info != null &&
                    info.isHit &&
                    info.distance >= 0 &&
                    info.distance < best.distance) {
                    best = info;
                    hits++;
                }
            }
        }
        best.hitCount = hits;
        return best;
    }
    getReflectionRay(p, n, v) {
        let c1 = -n.dot(v);
        let R1 = Vector.add(Vector.multiplyScalar(n, NUMBER_TWO * c1), v);
        return new Ray(p, R1);
    }
    rayTrace(info, ray, scene, depth) {
        // Calc ambient
        let color = Color.multiplyScalar(info.color, scene.background.ambience);
        let oldColor = color;
        let shininess = Math.pow(NUMBER_TEN, info.shape.material.gloss + 1);
        for (let i = 0; i < scene.lights.length; i++) {
            let light = scene.lights[i];
            // Calc diffuse lighting
            let v = Vector.subtract(light.position, info.position).normalize();
            if (this.options.renderDiffuse) {
                let L = v.dot(info.normal);
                if (L > 0.0) {
                    color = Color.add(color, Color.multiply(info.color, Color.multiplyScalar(light.color, L)));
                }
            }
            color = this.getColorByDepth(info, ray, scene, depth, color);
            // Render shadows and highlights
            let shadowInfo = new IntersectionInfo();
            color = this.renderShadowsAndHighlights(info, scene, shadowInfo, color, v, light, shininess);
        }
        color.limit();
        return color;
    }
    getColorByDepth(info, ray, scene, depth, color) {
        var _a, _b;
        // The greater the depth the more accurate the colours, but
        // this is exponentially (!) expensive
        if (depth <= this.options.rayDepth) {
            // calculate reflection ray
            if (this.options.renderReflections &&
                ((_a = info.shape) === null || _a === void 0 ? void 0 : _a.material.reflection) > 0) {
                let reflectionRay = this.getReflectionRay(info.position, info.normal, ray.direction);
                let refl = this.testIntersection(reflectionRay, scene, info.shape);
                if (refl.isHit && refl.distance > 0) {
                    refl.color = this.rayTrace(refl, reflectionRay, scene, depth + 1);
                }
                else {
                    refl.color = scene.background.color;
                }
                color = Color.blend(color, refl.color, (_b = info.shape) === null || _b === void 0 ? void 0 : _b.material.reflection);
            }
        }
        return color;
    }
    renderShadowsAndHighlights(info, scene, shadowInfo, color, v, light, shininess) {
        var _a, _b, _c, _d;
        if (this.options.renderShadows) {
            let shadowRay = new Ray(info.position, v);
            shadowInfo = this.testIntersection(shadowRay, scene, info.shape);
            if (shadowInfo.isHit && shadowInfo.shape !== info.shape) {
                let vA = Color.multiplyScalar(color, NUMBER_POINT_FIVE);
                let dB = NUMBER_POINT_FIVE *
                    Math.pow((_a = shadowInfo.shape) === null || _a === void 0 ? void 0 : _a.material.transparency, NUMBER_POINT_FIVE);
                color = Color.addScalar(vA, dB);
            }
        }
        // Phong specular highlights
        if (this.options.renderHighlights &&
            !shadowInfo.isHit &&
            ((_b = info.shape) === null || _b === void 0 ? void 0 : _b.material.gloss) > 0) {
            let lV = Vector.subtract(info.shape.position, light.position).normalize();
            let eVector = Vector.subtract(scene.camera.position, (_c = info.shape) === null || _c === void 0 ? void 0 : _c.position).normalize();
            let hVector = Vector.subtract(eVector, lV).normalize();
            let glossWeight = Math.pow(Math.max((_d = info.normal) === null || _d === void 0 ? void 0 : _d.dot(hVector), 0.0), shininess);
            color = Color.add(Color.multiplyScalar(light.color, glossWeight), color);
        }
        return color;
    }
}
// Defined class Options
class Options {
    constructor() {
        this.canvasHeight = DEFAULT_CANVAS_HEIGHT;
        this.canvasWidth = DEFAULT_CANVAS_WIDTH;
        this.pixelWidth = NUMBER_TWO;
        this.pixelHeight = NUMBER_TWO;
        this.renderDiffuse = false;
        this.renderShadows = false;
        this.renderHighlights = false;
        this.renderReflections = false;
        this.rayDepth = NUMBER_TWO;
    }
}
function initShapes(scene) {
    let sphere = new Sphere(new Vector(-NUMBER_ONE_POINT_FIVE, NUMBER_ONE_POINT_FIVE, NUMBER_TWO), NUMBER_ONE_POINT_FIVE, new Solid(new Color(0, NUMBER_POINT_FIVE, NUMBER_POINT_FIVE), NUMBER_POINT_THREE, 0.0, 0.0, NUMBER_TWO));
    let sphere1 = new Sphere(new Vector(1, NUMBER_POINT_TWO_FIVE, 1), NUMBER_POINT_FIVE, new Solid(new Color(NUMBER_POINT_NINE, NUMBER_POINT_NINE, NUMBER_POINT_NINE), NUMBER_POINT_ONE, 0.0, 0.0, NUMBER_ONE_POINT_FIVE));
    let plane = new Plane(new Vector(NUMBER_POINT_ONE, NUMBER_POINT_NINE, -NUMBER_POINT_FIVE).normalize(), NUMBER_ONE_POINT_TWO, new Chessboard(new Color(1, 1, 1), new Color(0, 0, 0), NUMBER_POINT_TWO, 0.0, 1, NUMBER_POINT_SEVEN));
    scene.shapes.push(sphere);
    scene.shapes.push(sphere1);
    scene.shapes.push(plane);
    return scene;
}
function initLights(scene) {
    let light = new Light(new Vector(NUMBER_FIVE, NUMBER_TEN, -1), new Color(NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT), 0);
    let light1 = new Light(new Vector(-NUMBER_THREE, NUMBER_FIVE, DEFAULT_VECTOR_Z), new Color(NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT, NUMBER_POINT_EIGHT), NUMBER_ONE_HUNDRED);
    scene.lights.push(light);
    scene.lights.push(light1);
    return scene;
}
function renderScene() {
    let scene = new Scene();
    scene.camera = new Camera(new Vector(0, 0, DEFAULT_VECTOR_Z), new Vector(-NUMBER_POINT_TWO, 0, NUMBER_FIVE), new Vector(0, 1, 0));
    scene.background = new Background(new Color(NUMBER_POINT_FIVE, NUMBER_POINT_FIVE, NUMBER_POINT_FIVE), NUMBER_POINT_FOUR);
    scene = initShapes(scene);
    scene = initLights(scene);
    let option = new Options();
    option.canvasWidth = DEFAULT_CANVAS_WIDTH;
    option.canvasHeight = DEFAULT_CANVAS_HEIGHT;
    option.pixelWidth = NUMBER_FIVE;
    option.pixelHeight = NUMBER_FIVE;
    option.renderDiffuse = true;
    option.renderHighlights = true;
    option.renderShadows = true;
    option.renderReflections = true;
    option.rayDepth = NUMBER_TWO;
    let raytracer = new Engine(option);
    raytracer.renderScene(scene);
    // debugLog("Create camera: ");
    // debugLog(scene.camera.toString());
    // debugLog("Create Background： ");
    // debugLog(scene.background.toString());
    // debugLog("Create sphere: ");
    // debugLog(sphere.toString());
    // debugLog("Create sphere1: ");
    // debugLog(sphere1.toString());
    // debugLog("Create plane: ");
    // debugLog(plane.toString());
    // debugLog("Create light: ");
    // debugLog(light.toString());
    // debugLog("Create light1: ");
    // debugLog(light1.toString());
    // debugLog("Create option ");
    // debugLog(String(option));
}
/**
 * @State
 * @Tags Jetstream2
 */
class Benchmark {
    /**
     * @Benchmark
     */
    runIteration() {
        for (let i = 0; i < NUMBER_FIVE; i++) {
            renderScene();
        }
    }
}
function run() {
    let startTime = Date.now();
    let raytrace = new Benchmark();
    for (let index = 0; index < MAX_RUN_TIMES; index++) {
        raytrace.runIteration();
    }
    let endTime = Date.now();
    console.log("raytrace: ms = " + (endTime - startTime));
}
run();
